/**
  ******************************************************************************
  * @file           
  * @author         古么宁
  * @brief          电力 101/104 规约头文件
  * @note
  * <pre>
  * 使用步骤：
  * 0> 初始化一次协议栈 iec10x_protocol_init();
  *    需要用户层提供 100ms 一次的 iec10x_periodic_handle() 轮询;
  * 
  * 1> 新建全局/静态总线 iec10x_t bus ;
  * 
  * 2> 编写对应的初始化结构体 struct iec101cfg buscfg ;
  * 
  * 3> 编写用户层数据处理相关函数注册 usrinit() ;
  * 
  * 3> iec10x_bus_init(&bus,101,&buscfg,usrinit); 初始化总线
  * 
  * 4> bus 接收到一包数据后，无论是否断包，交由 
  *     iec10x_recv(&bus,data,len) 处理
  * </pre>
  * 
  ******************************************************************************
  *
  * COPYRIGHT(c) 2019 GoodMorning
  *
  ******************************************************************************
*/
#ifndef _IEC10X_H_
#define _IEC10X_H_

// 必须依赖项
#include "iec_list.h"
#include "iec_sys.h"
#include "ASDUTYP.h"

/* Global  types ------------------------------------------------------------*/

/// 硬件发送对应类型
typedef void (*haltxfn_t)(unsigned char * info , int len);

/// 硬件重置函数类型
typedef void (*halrstfn_t)(void * iec10xbus); 

/// 调试信息打印函数类型
typedef int (*printf_t)(const char * fmt,...);

/// 104 基本配置属性值
typedef struct iec104cfg {
	/* 报文信息输出，只负责打印每帧报文的含义和类型 */
	haltxfn_t   haltx          ; ///< 硬件发送函数
	halrstfn_t  halrst         ; ///< 硬件重置函数
	printf_t    print          ; ///< 调试打印接口，可以定义为 printf
	int         period         ; ///< 周期，单位毫秒，作主机为周期总召，从机为周期上送
	int         sync_period    ; ///< 对时周期 

	uint16_t    t0             ; ///< 建立连接超时，单位毫秒
	uint16_t    t1             ; ///< 发送或测试 APDU 超时，即对方无响应超时，单位毫秒
	uint16_t    t2             ; ///< 接收连续 I 帧过程，无数据报文时发送确认报文时间，单位毫秒，t2 < t1 
	uint16_t    t3             ; ///< 长期空闲状态下发送测试帧的间隔，单位毫秒
	
	uint16_t    ctrl_wait      ; ///< 遥控等待时间
	uint16_t    cmnaddr        ; ///< 公共地址
	uint8_t     cmnaddrlen     ; ///< 公共地址长度
	uint8_t     causelen       ; ///< 传输原因长度
	
	uint8_t     dbglevel       ; ///< 调试等级
	uint8_t     infoaddrlen    ; ///< 信息结构体地址长度
	uint8_t     pktmax         ; ///< 单包最长长度
	uint8_t     mode           ; ///< 主从模式，SLAVE_MODE(0) 
	uint8_t     k              ; ///< 滑动窗口最大值，即发送序号和接收序号最大差值
	uint8_t     w              ; ///< 最迟接收到 w 个 I 帧后给出回复，即建议滑动窗口更新界限
}
iec104cfg_t;


/// 101 基本配置属性值
typedef struct iec101cfg {
	/* 报文信息输出，只负责打印每帧报文的含义和类型 */
	haltxfn_t   haltx          ; ///< 硬件发送函数
	halrstfn_t  halrst         ; ///< 硬件重置函数
	printf_t    print          ; ///< 调试打印接口，可以定义为 printf
	int         period         ; ///< 轮询/上送周期，单位毫秒 ms。
	int         sync_period    ; ///< 对时周期 

	uint16_t    heartbeat      ; ///< 非平衡（问答式）主机下，对从机进行轮询二级数据的周期
	uint16_t    txtimeout      ; ///< 发送最长等待时间，单位秒
	uint16_t    ctrl_wait      ; ///< 遥控等待时间
	
	uint16_t    localaddr      ; ///< 本地地址
	uint16_t    connectaddr    ; ///< 对端地址
	uint16_t    lnkaddr        ; ///< 公共地址
	uint16_t    cmnaddr        ; ///< 公共地址
	
	uint8_t     lnkaddrlen     ; ///< 链路地址长度，1或2字节，初始化后只读
	uint8_t     cmnaddrlen     ; ///< 公共地址长度，1或2字节，初始化后只读
	uint8_t     causelen       ; ///< 传输原因长度，1或2字节，初始化后只读
	uint8_t     infoaddrlen    ; ///< 信息结构体地址长度，2或3字节，初始化后只读
	
	uint8_t     dbglevel       ; ///< 调试等级
	uint8_t     pktmax         ; ///< 单包最长长度
	uint8_t     mode           ; ///< 主从模式，为 1 时作主机
	uint8_t     resendmax      ; ///< 主机最大重发次数
	uint8_t     balance        ; ///< 是否为平衡模式
	uint8_t     direct         ; ///< 平衡模式下的控制域发送方向
}
iec101cfg_t;

/// iec 总线定义
typedef struct iec10x {
	struct iec_list   node           ; ///< 串联所有协议栈的总线
	struct iec_list   event          ; ///< 突发事件表头,SOE
	struct iec_slist  device         ; ///< 设备接口根目录
	struct iec_slist  summon         ; ///< 总召类型表头，作子站时使用
	
	haltxfn_t         haltx          ; ///< 硬件发送函数
	halrstfn_t        halrst         ; ///< 硬件重置函数
	printf_t          printf         ; ///< 调试打印接口，可以定义为 printf

	struct iec_do {
		/* 中间参数及其状态机 */
		void  (*todo)(struct iec10x * this);
		size_t  argv[6]; 
	}
	operate                          , ///< 总召/对时/电度流程及其状态机
	ctrl                             ; ///< 遥控流程状态机
	
	#if (ENABLE_FILE_TRANS)
		/* 使能文件相关接口 */
		struct iec_do       fs           ; ///< 文件操作及其状态机
		struct iec_slist    filelist     ; ///< 文件夹列表
		char                workdir[128] ; ///< 当前的工作目录
		
		/* 中断当前文件操作，如当前正在进行文件传输但链路断开，则进入此回调 */
		void  (*interrupt)(struct iec10x * this);
	#endif

	size_t            onlinet        ; ///< 最后在线时间
	size_t            periodt        ; ///< 上一次周期上送时间戳
	size_t            synct          ; ///< 上一次对时的时间戳
	size_t            ctrlt          ; ///< 接收到遥控命令的时间戳
	int               data_period    ; ///< 数据周期，单位毫秒，作主机为周期总召，从机为周期上送
	int               sync_period    ; ///< 对时周期，单位毫秒

	unsigned char     txbuf[256+8]   ; ///< 发送缓存
	unsigned char     rxbuf[256+8]   ; ///< 接收缓存，处理断包粘包需要用到

	unsigned char     mode           ; ///< 模式,作 iec104s/iec104m/...
	unsigned char     busy           ; ///< 总线是否在传输数据或等待响应
	unsigned char     connected      ; ///< 连接状态
	unsigned char     ctrling        ; ///< 正在执行遥控
	unsigned char     cmnaddrlen     ; ///< 公共地址长度，1或2字节，初始化后只读
	unsigned char     causelen       ; ///< 传输原因长度，1或2字节，初始化后只读
	unsigned char     infoaddrlen    ; ///< 信息结构体地址长度，2或3字节，初始化后只读
	unsigned char     pktmax         ; ///< 单包最长长度

	unsigned short    rxtail         ; ///< 接收缓存区 rxbuf 末尾
	unsigned short    connectaddr    ; ///< 对端地址
	unsigned short    cmnaddr        ; ///< 对端设备的公共地址
	unsigned short    ctrl_wait      ; ///< 遥控等待时间
	unsigned short    t0             ; ///< 建立连接超时，单位毫秒
	unsigned short    t1             ; ///< 发送或测试 APDU 超时，即对方无响应超时，单位毫秒
	unsigned short    t2             ; ///< 接收连续 I 帧过程无数据报文时更新滑动窗口时间，单位毫秒，t2 < t1 
	unsigned short    t3             ; ///< 长期空闲状态下发送测试帧的间隔，即心跳包间隔，单位毫秒

	union {
		/// 以下字段为 104 总线相关特性
		struct iec104_property {
			unsigned short txnums    ; ///< 本机发送序号
			unsigned short txcheck   ; ///< 对端的接收序号
			unsigned short rxnums    ; ///< 本机接收序号
			unsigned short rxcheck   ; ///< 对端的发送序号
			unsigned char  k         ; ///< 滑动窗口最大值，即发送序号和接收序号最大差值
			unsigned char  w         ; ///< 最迟接收到 w 个 I 帧后给出回复，即建议滑动窗口更新界限
		} iec104;

		/// 以下字段为 101 总线相关特性
		struct iec101_property {
			void (*todo)(struct iec10x * this) ;
			unsigned short     lnaddr          ; ///< 链路地址，一般同公共地址。作主机时为对端的地址，作从机时为自己的地址
			unsigned char      lnaddrlen       ; ///< 链路地址长度，1或2字节
			unsigned char      lnctrl          ; ///< 链路控制域
			unsigned char      FCB             ; ///< 同 @see struct master_linkctrl 中的 FCB 位，无论作主从都需要进行此位的纪录
			unsigned char      resend          ; ///< 重发次数
			unsigned char      resendmax       ; ///< 最大重发次数
		} iec101;
	};

	unsigned char     dbglevel      ; ///< bus->printf() 中的使能等级 ，@see enum IEC_DEBUG_LEVEL

	iec_sys_mutex_t   mtx           ; ///< 总线多线程互斥量支持 @see dev_sys.h
}
iec10x_t;

/// 函数指针
typedef void(*iec_fn_t)(iec10x_t * bus);

/**
  * @brief    获取用户层信息体地址列表及个数接口定义
  * @param    bus       : 当前总线
  * @param    addrlist  : 获取当前总线的信息体地址列表
  * @param    datanum   : 获取当前总线的信息体个数，也即为 addrlist 元素个数
  * @return   成功返回 0
*/
typedef int (*dev_statfn_t)(void * bus ,uint32_t ** addrlist , int * datanum); 

/**
  * @brief    获取用户层信息体内容
  * @param    bus       : 当前总线
  * @param    addr      : 指定读取信息体的地址
  * @param    data      : 用户层信息体内容
  * @return   成功返回 0
*/
typedef int (*dev_getfn_t)(void * bus ,uint32_t addr , void * data); 


/**
  * @brief    用户层信息体接收处理
  * @note     即协议栈接收到一帧用户数据后需要调用的用户数据处理
  * @param    bus       : 当前总线
  * @param    COT       : 传输原因
  * @param    nums      : 用户结构体个数
  * @param    addrlist  : 当前帧用户结构体地址列表
  * @param    data      : 当前帧用户层信息体内容
  * @return   成功返回 0
*/
typedef int (*dev_profn_t)(void * bus,int COT,int nums ,uint32_t * addrlist , void * data); 


/** 协议栈调试等级 */
enum IEC_DEBUG_LEVEL {
	IEC_DBG_NULL = 0 , ///< 关闭协议栈所有的打印输出
	IEC_DBG_LOW      , ///< 仅保留交互打印，如打印流程的告警信息等
	IEC_DBG_MIDDLE   , ///< 打印报文的字节流和帧类型等基本信息
	IEC_DBG_HIGH     , ///< 打印报文的详细信息，详细至数据帧的基本内容值
	IEC_DBG_ALL      , ///< 打印协议栈可以解析到的全部信息
};

/* Global  macro ------------------------------------------------------------*/

/**
  * @brief    总线对象初始化，设定总线模式和硬件输出
  * @param    bus        : 主机总线对象
  * @param    typ        : 101/104
  * @param    cfg        : 初始化参数
  * @param    fn         : 用户初始化回调函数，比如注册各类数据响应
  * @note     实际上也是调用了 iec101_bus_init /iec104_bus_init ，此处是为了统一 iec10x 前缀
  * @return   don't care
*/
#define iec10x_bus_init(bus,typ,cfg,fn)    iec##typ##_bus_init(bus,cfg,fn)
void    iec101_bus_init(iec10x_t * bus,const iec101cfg_t * cfg , iec_fn_t  devinitfn) ;
void    iec104_bus_init(iec10x_t * bus,const iec104cfg_t * cfg , iec_fn_t  devinitfn) ;


/** 包解析 */
#define IEC_PKT_PARSER(bus,...)  do{if((bus)->parser) (bus)->parser(__VA_ARGS__);}while(0)

/* Global  variables --------------------------------------------------------*/

extern size_t iec_timestamp;

/* Global  function prototypes ----------------------------------------------*/
/**
  * @brief    协议栈初始化，生成响应链表
  * @param    void
  * @return   don't care
*/
void iec10x_protocol_init(void) ;

/**
  * @brief    定时轮询,此函数需要用户实现周期调用一次，建议 100ms / 50ms / 25ms
  * @note     此函数的轮询周期也决定了总线的发送频率，即每包的间隔
  * @param    localtime  : 毫秒精度的系统时间戳
  * @return   don't care
*/
void iec10x_periodic_handle(size_t localtime);

/**
  * @brief    断开连接
  * @param    bus : 需要断开连接的总线
  * @return   don't care
*/
void iec10x_disconnect(iec10x_t * bus);


/**
  * @brief    总线对象去初始化，释放总线状态及其内存
  * @param    bus        : 主机总线对象
  * @return   don't care
*/
int  iec10x_bus_deinit(iec10x_t * bus) ;

/**
  * @brief    总线接收数据并解析
  * @param    bus   : 需要解析的总线结构体 
  * @param    data  : 包数据 
  * @param    len   : 包数据长度
  * @return   don't care
*/
void iec10x_recv(iec10x_t * bus , uint8_t * data , int len);

/**
  * @brief    用户设备接口管理注册
  * @param    bus      : 目标总线
  * @param    type     : 当前数据接口的类型
  * @param    statfn   : 信息结构体状态获取接口
  * @param    getfn    : 信息体结构体内容获取接口
  * @param    profn    : 信息体结构体内容处理接口
  * @return   成功返回 0 ，否则返回其他
*/
int iec10x_devif_register(
	iec10x_t *         bus       ,
	unsigned char      type      ,
	dev_statfn_t       statfn    ,
	dev_getfn_t        getfn     ,
	dev_profn_t        profn   ) ;


/**
  * @brief    从站添加总召响应类型
  * @param    bus      : 总线
  * @param    grp      : 响应组， @see enum iecgrp
  * @param    type     : 响应类型， @see enum asdutype
  * @return   成功返回0 ，否则返回其他
*/
int iec10xslv_summon_add(iec10x_t * bus ,enum iecgrp grp ,unsigned char type);


/**
  * @brief    从机产生一个 soe 事件。
  * @param    bus      : 发生突发事件的总线 
  * @param    type     : 事件类型， @see slv_event_t
  * @param    infoaddr : 事件信息体地址
  * @param    data     : 事件信息体具体内容,如 SIQ24T_t / DIQ24T_t
  * @return   成功返回0 ， 否则返回其他
*/
int iec10xslv_event(iec10x_t * bus ,slv_event_t type, uint32_t infoaddr,void * data);

// ---------------------------- telectrl ----------------------------

/// 遥控类型
typedef enum telectrl_type{
	SIQ_SET  = C_SC_NA_1, ///< 单点控制命令
	DIQ_SET  = C_DC_NA_1, ///< 双点控制
	VTI_SET  = C_RC_NA_1, ///< 步调节命令
	NVA_SET  = C_SE_NA_1, ///< 设置 NVA 命令
	SVA_SET  = C_SE_NB_1, ///< 设置 SVA 命令
	STD_SET  = C_SE_NC_1, ///< 设置 STD 命令
	S32_BIT  = C_BO_NC_1, ///< 设置 32bit 命令
}
telectrl_t;

/// 遥控结果
typedef enum ctrlresult {
	CTRL_SUCCESS = 0 , ///< 遥控成功
	CTRL_TIMEOUT     , ///< 遥控超时错误
	CTRL_ERROR       , ///< 从机返回遥控错误
	CTRLADDR_ERR     , ///< 从机返回遥控地址错误
}
ctrl_res_t ;

/// 遥控回调函数类型，在遥控成功或失败后调用
typedef int (*ctrlcallback_t)(
	void *         bus         ,
	telectrl_t     ctrltype    ,
	uint32_t       ctrladdr    ,
	uint32_t       ctrlval     , 
	ctrl_res_t     result    ) ;


/**
  * @brief    主站发送一个遥控请求
  * @param    bus        : 总线
  * @param    ctrladdr   : 遥控结构体对象所在地址
  * @param    ctrltype   : 遥控类型，参考 @see telectrl_t 
  * @param    ctrlval    : 遥控值，即设置 ctrladdr 为什么值
  * @param    callbackfn : 遥控回调，遥控结束后执行，详细定义参考 @see ctrlcallback_t
  * @return   don't care
*/
void iec10xmst_telectrl_req(
	iec10x_t *     bus         ,
	telectrl_t     ctrltype    ,
	uint32_t       ctrladdr    ,
	uint32_t       ctrlval     , 
	ctrlcallback_t callbackfn );


/**
  * @brief    主机设备请求召唤数据。
  * @param    bus    : 需要解析的总线结构体 
  * @param    group  : 请求组，参考@see summon_grp_t
  * @return   don't care
*/
void iec10xmst_summon_req(iec10x_t * bus , enum iecgrp  group);


#if (ENABLE_FILE_TRANS)
/* ------------------------ 如果使能了文件传输服务 ------------------------ */

#include "CPxTime2a.h"

/// 文件夹所有文件信息节点
struct file_node {
	struct iec_slist      node     ; ///< 节点
	int                   fsize    ; ///< file size
	struct CP56Time2a     ftime    ; ///< file time
	unsigned char         namelen  ; ///< file name length
	char                  name[1]  ; ///< file name
};


/**
  * @brief    文件夹操作回调
  * @param    bus       : 当前总线
  * @param    listhead  : 文件列表表头，串联 struct file_node 的 node 节点。
  * @param    result    : 结果
*/
typedef void (*dir_callbackfn_t)(void * bus , struct iec_slist * listhead , int result) ;

/**
  * @brief    文件操作回调
  * @param    bus       : 当前总线
  * @param    file      : 文件名
  * @param    result    : 结果
*/
typedef void (*fs_callbackfn_t)(void * bus , const char * file , int result) ;


/**
  * @brief    切换当前总线工作目录
  * @param    bus    : 总线
  * @param    dir    : 路径
  * @return   成功返回 0
*/
int iec10x_change_dir(iec10x_t * bus , const char * dir) ;


/**
  * @author   古么宁
  * @brief    主机召唤从机的某个文件
  * @param    bus        : 总线
  * @param    savedir    : 接收文件需要保存的本地文件夹
  * @param    file       : 读取从机的文件名
  * @param    callbackfn : 接收文件结束后的回调
  * @return   成功返回 0
*/
int iec10xmst_fget(
	iec10x_t *      bus          ,
	const char *    savedir      ,
	const char *    file         ,
	fs_callbackfn_t callbackfn ) ;


/**
  * @author   古么宁
  * @brief    主机下发文件至从站
  * @param    bus        : 总线
  * @param    dir        : 需要发送的文件所在的文件夹
  * @param    file       : 需要发送的文件名
  * @param    callbackfn : 发送文件结束后的回调
  * @return   成功返回 0
*/
int iec10xmst_fput(
	iec10x_t *      bus          ,
	const char *    dir          ,
	const char *    file         ,
	fs_callbackfn_t callbackfn ) ;


/**
  * @author   古么宁
  * @brief    主机召唤从机的某个目录
  * @param    bus        : 总线
  * @param    dirname    : 需要召唤的从机目录名
  * @param    tfilter    : 为 0 时搜索所有文件，否则搜索时间段文件
  * @param    start      : tfilter 为 1 时需输入起始查询时间
  * @param    end        : tfilter 为 1 时需输入终止查询时间
  * @param    callbackfn : 召唤文件目录结束后的回调
  * @return   成功返回 0
*/
int iec10xmst_readdir(
	iec10x_t *        bus          ,
	const char *      dirname      ,
	int               tfilter      ,
	CP56Time2a_t *    start        ,
	CP56Time2a_t *    end          ,
	dir_callbackfn_t  callbackfn ) ;

#endif /* #if (ENABLE_FILE_TRANS) 如果使能了文件传输服务  */




#endif /* _IEC10X_H_ */
